package com.cyj.dream.gateway.handler;

import com.cyj.dream.core.constant.enums.ResultCode;
import com.cyj.dream.core.constant.pagemodel.ResponseUtil;
import com.cyj.dream.core.exception.BizException;
import com.cyj.dream.core.util.pojo.ErrorResult;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
import reactor.core.publisher.Mono;

import javax.servlet.http.HttpServletRequest;

/**
 * @Description: 网关异常通用处理器，只作用在webflux 环境下 , 优先级低于 {@link ResponseStatusExceptionHandler} 执行
 * @BelongsProject: DreamChardonnay
 * @BelongsPackage: com.cyj.dream.gateway.handler
 * @Author: ChenYongJia
 * @CreateTime: 2021-09-27 12:43
 * @Email: chen87647213@163.com
 * @Version: 1.0
 */
@Slf4j
@Order(-1)
@RequiredArgsConstructor
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {

    private final ObjectMapper objectMapper;

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();

        if (response.isCommitted()) {
            return Mono.error(ex);
        }

        // header set
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        if (ex instanceof ResponseStatusException) {
            response.setStatusCode(((ResponseStatusException) ex).getStatus());
        }

        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory bufferFactory = response.bufferFactory();
            try {
                return bufferFactory.wrap(objectMapper.writeValueAsBytes(ResponseUtil.error(ex.getMessage())));
            }
            catch (JsonProcessingException e) {
                log.error("Error writing response", ex);
                return bufferFactory.wrap(new byte[0]);
            }
        }));
    }

    /**
     * 处理自定义的业务异常
     * RestControllerAdvice 1.全局异常处理 （@ExceptionHandler）
     * 2.全局数据绑定 （@InitBinder）
     * 3.全局数据预处理 （@ModelAttribute）
     *
     * @param e       异常对象
     * @param request request
     * @return 错误结果
     */
    @ExceptionHandler(BizException.class)
    public ErrorResult bizExceptionHandler(BizException e, HttpServletRequest request) {
        log.error("发生业务异常！原因是: {}", e.getMessage());
        return ErrorResult.fail(e.getCode(), e.getMessage());
    }

    /**
     * 拦截抛出的异常，@ResponseStatus：用来改变响应状态码
     *
     * @param e
     * @param request
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Throwable.class)
    public ErrorResult handlerThrowable(Throwable e, HttpServletRequest request) {
        log.error("发生未知异常！原因是: {}", e);
        // 系统错误
        ErrorResult error = ErrorResult.fail(ResultCode.SYSTEM_ERROR, e);
        return error;
    }

    /**
     * 参数校验异常
     *
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(BindException.class)
    public ErrorResult handleBindExcpetion(BindException e, HttpServletRequest request) {
        log.error("发生参数校验异常！原因是：{}", e);
        ErrorResult error = ErrorResult.fail(ResultCode.PARAM_IS_INVALID, e, e.getAllErrors().get(0).getDefaultMessage());
        return error;
    }

    /**
     * 句柄方法参数无效异常
     *
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        log.error("发生参数校验异常！原因是：{}", e);
        ErrorResult error = ErrorResult.fail(ResultCode.PARAM_IS_INVALID, e, e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        return error;
    }

    /**
     * 处理空指针的异常
     *
     * @param req 无用
     * @param e   异常
     * @return
     */
    @ExceptionHandler(value = NullPointerException.class)
    public ErrorResult exceptionHandler(HttpServletRequest req, NullPointerException e) {
        log.error("发生空指针异常！原因是：{}", e);
        return ErrorResult.fail(ResultCode.BODY_NOT_MATCH, e);
    }

    /**
     * token过期异常
     *
     * @param req 无用
     * @param e   异常
     * @return
     */
    /*@ExceptionHandler(value = ExpiredJwtException.class)
    public ErrorResult tokenOverdueHandler(HttpServletRequest req, NullPointerException e) {
        log.error("发生token过期异常异常！原因是：{}", e);
        return ErrorResult.fail(ResultCode.USER_TOKEN_OVERDUE, e);
    }*/

}
